const mouse = {
	x: 0,
	y: 0,
	down: false,
	init: function () {
		$('#gamecanvas').mousemove(mouse.mousemovehandler);
		$('#gamecanvas').mousedown(mouse.mousedownhandler);
		$('#gamecanvas').mouseup(mouse.mouseuphandler);
		$('#gamecanvas').mouseout(mouse.mouseuphandler);
	},
	mousemovehandler: function (ev) {
		let offset = $('#gamecanvas').offset();
		mouse.x = ev.pageX - offset.left;
		mouse.y = ev.pageY - offset.top;
		if (mouse.down) {
			mouse.dragging = true;
		}
	},
	mousedownhandler: function (ev) {
		console.log("mouse down");
		mouse.down = true;
		mouse.downX = mouse.x;
		mouse.downY = mouse.y;
		ev.originalEvent.preventDefault();
	},
	mouseuphandler: function (ev) {
		mouse.down = false;
		mouse.dragging = false;
	}
};
const game = {
	mode: "intro",
	shotX: 140,
	shotY: 280,
	maxSpeed: 3,
	minOffset: 0,
	maxOffset: 300,
	offsetLeft: 0,
	score: 0,
	init: () => {
		game.canvas = $('#gamecanvas')[0];
		game.context = game.canvas.getContext('2d');

		levels.init();
		loader.init();
		mouse.init();

		game.loadSounds(function () {
			$('.gamelayer').hide();
			$('#gamestartscreen').show();
		})
	},
	startBackgroundMusic: function() {
        game.backgroundMusic.play();
        game.setBackgroundMusicButton();
    },
	stopBackgroundMusic: function () {
		game.backgroundMusic.pause();
		// Go to the beginning of the song
		game.backgroundMusic.currentTime = 0;

		game.setBackgroundMusicButton();
	},

	toggleBackgroundMusic: function () {
		if (game.backgroundMusic.paused) {
			game.backgroundMusic.play();
		} else {
			game.backgroundMusic.pause();
		}

		game.setBackgroundMusicButton();
	},

	setBackgroundMusicButton: function () {
		var toggleImage = document.getElementById("togglemusic");

		if (game.backgroundMusic.paused) {
			toggleImage.src = "img/nosound.png";
		} else {
			toggleImage.src = "img/sound.png";
		}
	},
	loadSounds: function (onload) {
		game.backgroundMusic = loader.loadSound("audio/gurdonark-kindergarten");

		game.slingshotReleasedSound = loader.loadSound("audio/released");
		game.bounceSound = loader.loadSound("audio/bounce");
		game.breakSound = {
			"glass": loader.loadSound("audio/glassbreak"),
			"wood": loader.loadSound("audio/woodbreak")
		};

		loader.onload = onload;
	},
	showLevelScreen: function () {
		$('.gamelayer').hide();
		$('#levelselectscreen').show('slow');
	},
	start: function () {
		$('.gamelayer').hide();
		$('#gamecanvas').show();
		$('#scorescreen').show();

		game.startBackgroundMusic();

		game.mode = "intro";
		game.offsetLeft = 0;
		game.ended = false;
		game.animationFrame = window.requestAnimationFrame(game.animate, game.canvas);
	},
	handlePanning: function () {
		game.offsetLeft++;
	},
	animate: function () {
		// 移动背景图
		game.handlePanning();
		// 移动英雄
		// 因为 rAF 在不同浏览器不同电脑间调用 animate 方法的频率不同，因此可以测量上一次调用 animation 后的等待时间，作为步长传给 box2d
		let currentTime = new Date().getTime();
		let timeStep;
		if (game.lastUpdateTime) {
			timeStep = (currentTime - game.lastUpdateTime) / 1000;

			box2d.step(timeStep);
		}
		game.lastUpdateTime = currentTime;
		// 绘制背景
		game.context.drawImage(game.currentLevel.backgroundImg, game.offsetLeft / 4, 0, 640, 480, 0, 0, 640, 480);
		game.context.drawImage(game.currentLevel.foregroundImg, game.offsetLeft, 0, 640, 480, 0, 0, 640, 480);
		// 绘制弹弓
		game.context.drawImage(game.shotImg, game.shotX - game.offsetLeft, game.shotY);

		// 绘制物体
		game.drawAllBodies();

		// 绘制弹弓胶带
		if (game.mode == "firing") {
			game.drawSlingshotBand();
		}

		// 绘制弹弓外侧支架
		game.context.drawImage(game.foreshotImg, game.shotX - game.offsetLeft, game.shotY);

		if (!game.ended) {
			game.animationFrame = window.requestAnimationFrame(game.animate, game.canvas);
		}
	},
	// 移动画面到某个中心点
	panTo: function (newCenter) {
		if (Math.abs(newCenter - game.offsetLeft - game.canvas.width / 4) > 0 && game.offsetLeft <= game.maxOffset && game.offsetLeft >= game.minOffset) {
			let deltaX = Math.round((newCenter - game.offsetLeft - game.canvas.width / 4) / 2);
			if (deltaX && Math.abs(deltaX) > game.maxSpeed) {
				deltaX = game.maxSpeed * Math.abs(deltaX) / (deltaX);
			}
			game.offsetLeft += deltaX;
		} else {
			return true;
		}
		if (game.offsetLeft < game.minOffset) {
			game.offsetLeft = game.minOffset;
			return true;
		} else if (game.offsetLeft > game.maxOffset) {
			game.offsetLeft = game.maxOffset;
			return true;
		}
		return false;
	},
	// 处理各个阶段的画面移动
	handlePanning: function () {
		if (game.mode == "intro") {
			if (game.panTo(700)) {
				game.mode = "load-next-hero";
			}
		}
		if (game.mode == "wait-for-firing") {
			if (mouse.dragging) {
				if (game.mouseOnCurrentHero()) {
					game.mode = "firing";
				} else {
					game.panTo(mouse.x + game.offsetLeft);
				}
			} else {
				game.panTo(game.shotX);
			}
		}
		if (game.mode == "load-next-hero") {
			game.countHeroesAndVillains();

			if (game.villains.length == 0) {
				game.mode = "level-success";
				return;
			}
			if (game.heroes.length == 0) {
				game.mode = "level-failure";
				return;
			}
			if (!game.currentHero) {
				game.currentHero = game.heroes[game.heroes.length - 1];
				game.currentHero.SetPosition({ x: 180 / box2d.scale, y: 200 / box2d.scale });
				game.currentHero.SetLinearVelocity({ x: 0, y: 0 });
				game.currentHero.SetAngularVelocity(0);
				game.currentHero.SetAwake(true);
			} else {
				game.panTo(game.shotX);
				if (!game.currentHero.IsAwake()) {
					game.mode = "wait-for-firing";
				}
			}
		}
		if (game.mode == "firing") {
			if (mouse.down) {
				game.panTo(game.shotX);
				game.currentHero.SetPosition({ x: (mouse.x + game.offsetLeft) / box2d.scale, y: mouse.y / box2d.scale })
			} else {
				game.mode = "fired";
				game.slingshotReleasedSound.play();
				// 计算出冲击力，应用到英雄上
				let impulseScaleFactor = .75;
				let impulse = new b2Vec2((game.shotX + 35 - mouse.x - game.offsetLeft) * impulseScaleFactor, (game.shotY + 25 - mouse.y) * impulseScaleFactor);
				game.currentHero.ApplyImpulse(impulse, game.currentHero.GetWorldCenter());
			}
			game.panTo(game.shotX);
		}
		if (game.mode == "fired") {
			// 让画面跟随英雄
			let heroX = game.currentHero.GetPosition().x * box2d.scale;
			game.panTo(heroX);
			if ((!game.currentHero.IsAwake() || heroX < 0 || heroX > game.currentLevel.foregroundImg.width)) {
				// 
				box2d.world.DestroyBody(game.currentHero);
				game.currentHero = null;
				game.mode = "load-next-hero";
			}
		}
		if (game.mode == "level-success" || game.mode == "level-failure") {
			if (game.panTo(0)) {
				game.ended = true;
				game.showEndingScreen();
			}
		}
	},
	drawAllBodies: function () {
		box2d.world.DrawDebugData();

		for (let body = box2d.world.GetBodyList(); body; body = body.GetNext()) {
			let entity = body.GetUserData();
			if (entity) {
				let entityX = body.GetPosition().x * box2d.scale;
				if (entityX < 0 || entityX > game.currentLevel.foregroundImg.width || (entity.health && entity.health < 0)) {
					box2d.world.DestroyBody(body);
					if (entity.type == "villain") {
						game.score += entity.calories;
						$('#score').html = ('Score: ' + game.score);
					}
					if (entity.breakSound) {
						entity.breakSound.play();
					}
				} else {
					entities.draw(entity, body.GetPosition(), body.GetAngle());
				}
			}
		}
	},
	countHeroesAndVillains: function () {
		game.heroes = [];
		game.villains = [];
		for (let body = box2d.world.GetBodyList(); body; body = body.GetNext()) {
			var entity = body.GetUserData();

			if (entity) {
				if (entity.type === "hero") {
					game.heroes.push(body);
				} else if (entity.type === "villain") {
					game.villains.push(body);
				}
			}
		}
	},
	mouseOnCurrentHero: function () {
		// 检查鼠标是否放在英雄上,因为英雄都是圆形的,所以检查半径
		if (!game.currentHero) {
			return false;
		}
		let position = game.currentHero.GetPosition();
		let distanseSquared = Math.pow(position.x * box2d.scale - mouse.x - game.offsetLeft, 2) + Math.pow(position.y * box2d.scale - mouse.y, 2);
		let radiusSquared = Math.pow(game.currentHero.GetUserData().radius, 2);
		return (distanseSquared <= radiusSquared);
	},
	showEndingScreen: function () {
		var playNextLevel = document.getElementById("playnextlevel");
		var endingMessage = document.getElementById("endingmessage");

		if (game.mode === "level-success") {
			if (game.currentLevel.number < levels.data.length - 1) {
				endingMessage.innerHTML = "Level Complete. Well Done!!!";
				// More levels available. Show the play next level button
				playNextLevel.style.display = "block";
			} else {
				endingMessage.innerHTML = "All Levels Complete. Well Done!!!";
				// No more levels. Hide the play next level button
				playNextLevel.style.display = "none";
			}
		} else if (game.mode === "level-failure") {
			endingMessage.innerHTML = "Failed. Play Again?";
			// Failed level. Hide the play next level button
			playNextLevel.style.display = "none";
		}

		$("#endingscreen").show();

		// Stop the background music when the game ends
		game.stopBackgroundMusic();
	},
	drawSlingshotBand: function () {
		game.context.strokeStyle = "rgb(68,31,11)"; // Dark brown color
		game.context.lineWidth = 7; // Draw a thick line

		// Use angle hero has been dragged and radius to calculate coordinates of edge of hero wrt. hero center
		var radius = game.currentHero.GetUserData().radius + 1; // 1px extra padding
		var heroX = game.currentHero.GetPosition().x * box2d.scale;
		var heroY = game.currentHero.GetPosition().y * box2d.scale;
		var angle = Math.atan2(game.shotY + 25 - heroY, game.shotX + 50 - heroX);

		// This is the X, Y position of the point where the band touches the hero
		var heroFarEdgeX = heroX - radius * Math.cos(angle);
		var heroFarEdgeY = heroY - radius * Math.sin(angle);

		game.context.beginPath();
		// Start line from top of slingshot (the back side)
		game.context.moveTo(game.shotX - game.offsetLeft+50, game.shotY + 25);

		// Draw line to center of hero
		game.context.lineTo(heroX - game.offsetLeft, heroY);
		game.context.stroke();

		// Draw the hero on the back band
		entities.draw(game.currentHero.GetUserData(), game.currentHero.GetPosition(), game.currentHero.GetAngle());

		game.context.beginPath();
		// Move to edge of hero farthest from slingshot top
		game.context.moveTo(heroFarEdgeX - game.offsetLeft, heroFarEdgeY);

		// Draw line back to top of slingshot (the front side)
		game.context.lineTo(game.shotX - game.offsetLeft + 10, game.shotY + 30);
		game.context.stroke();
	},
	restartLevel: function () {
		window.cancelAnimationFrame(game.animationFrame);
		game.lastUpdateTime = null;
		levels.load(game.currentLevel.number);
	},

	startNextLevel: function () {
		window.cancelAnimationFrame(game.animationFrame);
		game.lastUpdateTime = null;
		levels.load(game.currentLevel.number + 1);
	},
}
const levels = {
	data: [
		{
			foreground: 'desert-foreground',
			background: 'clouds-background',
			entities: [
				// The ground
				{ type: "ground", name: "dirt", x: 500, y: 440, width: 1000, height: 20, isStatic: true },
				// The slingshot wooden frame
				{ type: "ground", name: "wood", x: 190, y: 390, width: 30, height: 80, isStatic: true },

				{ type: "block", name: "wood", x: 500, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "glass", x: 500, y: 280, angle: 90, width: 100, height: 25 },
				{ type: "villain", name: "burger", x: 500, y: 205, calories: 590 },

				{ type: "block", name: "wood", x: 800, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "glass", x: 800, y: 280, angle: 90, width: 100, height: 25 },
				{ type: "villain", name: "fries", x: 800, y: 205, calories: 420 },

				{ type: "hero", name: "orange", x: 80, y: 405 },
				{ type: "hero", name: "apple", x: 140, y: 405 }
			]
		}, {
			foreground: 'desert-foreground',
			background: 'clouds-background',
			entities: [
				// The ground
				{ type: "ground", name: "dirt", x: 500, y: 440, width: 1000, height: 20, isStatic: true },
				// The slingshot wooden frame
				{ type: "ground", name: "wood", x: 190, y: 390, width: 30, height: 80, isStatic: true },

				{ type: "block", name: "wood", x: 850, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "wood", x: 700, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "wood", x: 550, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "glass", x: 625, y: 316, width: 150, height: 25 },
				{ type: "block", name: "glass", x: 775, y: 316, width: 150, height: 25 },

				{ type: "block", name: "glass", x: 625, y: 252, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "glass", x: 775, y: 252, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "wood", x: 700, y: 190, width: 150, height: 25 },

				{ type: "villain", name: "burger", x: 700, y: 152, calories: 590 },
				{ type: "villain", name: "fries", x: 625, y: 405, calories: 420 },
				{ type: "villain", name: "sodacan", x: 775, y: 400, calories: 150 },

				{ type: "hero", name: "strawberry", x: 30, y: 415 },
				{ type: "hero", name: "orange", x: 80, y: 405 },
				{ type: "hero", name: "apple", x: 140, y: 405 }
			]
		},
		{
			foreground: 'desert-foreground',
			background: 'clouds-background',
			entities: [
				// The ground
				{ type: "ground", name: "dirt", x: 500, y: 440, width: 1000, height: 20, isStatic: true },
				// The slingshot wooden frame
				{ type: "ground", name: "wood", x: 190, y: 390, width: 30, height: 80, isStatic: true },

				{ type: "block", name: "wood", x: 850, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "wood", x: 700, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "wood", x: 550, y: 380, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "glass", x: 625, y: 316, width: 150, height: 25 },
				{ type: "block", name: "glass", x: 775, y: 316, width: 150, height: 25 },

				{ type: "block", name: "glass", x: 625, y: 252, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "glass", x: 775, y: 252, angle: 90, width: 100, height: 25 },
				{ type: "block", name: "wood", x: 700, y: 190, width: 150, height: 25 },

				{ type: "villain", name: "burger", x: 700, y: 152, calories: 590 },
				{ type: "villain", name: "fries", x: 625, y: 405, calories: 420 },
				{ type: "villain", name: "sodacan", x: 775, y: 400, calories: 150 },

				{ type: "hero", name: "strawberry", x: 30, y: 415 },
				{ type: "hero", name: "orange", x: 80, y: 405 },
				{ type: "hero", name: "apple", x: 140, y: 405 }
			]
		}
	],
	init: () => {
		let html = '';
		for (let i = 0; i < levels.data.length; i++) {
			const level = levels.data[i];
			html += `<input type="button" value="${i + 1}">`;
		};
		$('#levelselectscreen').html(html);

		$('#levelselectscreen input').click(function () {
			// 如果是用箭头函数，则 this 指向全局对象 window 因此不使用箭头函数，让 this 指向当前 levels 对象
			levels.load(this.value - 1);
			$('#levelselectscreen').hide();
		})
	},
	load: function (number) {
		// 初始化 2d 世界
		box2d.init();

		// 加载某一关的数据
		game.currentLevel = { number, hero: [] };
		game.score = 0;
		$('#score').html('Score: ' + game.score);
		game.currentHero = null;
		let level = levels.data[number];

		// 加载背景等
		game.currentLevel.backgroundImg = loader.loadImage("img/" + level.background + ".png");
		game.currentLevel.foregroundImg = loader.loadImage("img/" + level.foreground + ".png");
		game.shotImg = loader.loadImage("img/shot.png");
		game.foreshotImg = loader.loadImage("img/foreshot.png");

		// Load all the entities
		for (let i = level.entities.length - 1; i >= 0; i--) {
			var entity = level.entities[i];

			entities.create(entity);
		}

		if (loader.loaded) {
			game.start();
		} else {
			loader.onload = game.start;
		}
	}
};
const loader = {
	loaded: true,
	loadedCount: 0,
	totalCount: 0,
	soundFileExtn: ".ogg",
	init: function () {
		// 检查浏览器是否支持格式
		let mp3Flag, oggFlag;
		let audio = document.createElement('audio');
		if (audio.canPlayType) {
			mp3Flag = "" != audio.canPlayType('audio/mpeg');
			oggFlag = "" != audio.canPlayType('audio/ogg; codecs = "vorbis"');
		} else {
			mp3Flag = false;
			oggFlag = false;
		}
		// 检查 ogg MP3 都不支持就设为 null
		loader.soundFileExtn = oggFlag ? ".ogg" : mp3Flag ? ".mp3" : null;
	},
	loadImage: function (url) {
		this.totalCount++;
		this.loaded = false;
		$('#loadingscreen').show();
		let image = new Image();
		image.src = url;
		image.onload = loader.itemLoaded;
		return image;
	},
	loadSound: function (url) {
		this.totalCount++;
		this.loaded = false;
		$('#loadingscreen').show();
		let audio = new Audio();
		audio.src = url + loader.soundFileExtn;
		audio.addEventListener("canplaythrough", loader.itemLoaded, false);
		return audio;
	},
	itemLoaded: function () {
		loader.loadedCount++;
		$('#loadingmessage').html('Loaded ' + loader.loadedCount + ' of ' + loader.totalCount);
		if (loader.loadedCount == loader.totalCount) {
			loader.loaded = true;
			$('#loadingscreen').hide();
			if (loader.onload) {
				loader.onload();
				loader.onload = null;
			}
		}
	}
}
const entities = {
	definitions: {
		"glass": {
			fullHealth: 100,
			density: 2.4,
			friction: 0.4,
			restitution: 0.15
		},
		"wood": {
			fullHealth: 500,
			density: 0.7,
			friction: 0.4,
			restitution: 0.4
		},
		"dirt": {
			density: 3.0,
			friction: 1.5,
			restitution: 0.2
		},
		"burger": {
			shape: "circle",
			fullHealth: 40,
			radius: 25,
			density: 1,
			friction: 0.5,
			restitution: 0.4
		},
		"sodacan": {
			shape: "rectangle",
			fullHealth: 80,
			width: 40,
			height: 60,
			density: 1,
			friction: 0.5,
			restitution: 0.7
		},
		"fries": {
			shape: "rectangle",
			fullHealth: 50,
			width: 40,
			height: 50,
			density: 1,
			friction: 0.5,
			restitution: 0.6
		},
		"apple": {
			shape: "circle",
			radius: 25,
			density: 1.5,
			friction: 0.5,
			restitution: 0.4
		},
		"orange": {
			shape: "circle",
			radius: 25,
			density: 1.5,
			friction: 0.5,
			restitution: 0.4
		},
		"strawberry": {
			shape: "circle",
			radius: 15,
			density: 2.0,
			friction: 0.5,
			restitution: 0.4
		}
	},

	// Take the entity, create a Box2D body, and add it to the world
	create: function (entity) {
		var definition = entities.definitions[entity.name];

		if (!definition) {
			console.log("Undefined entity name", entity.name);

			return;
		}

		switch (entity.type) {
			case "block": // simple rectangles
				entity.health = definition.fullHealth;
				entity.fullHealth = definition.fullHealth;
				entity.shape = "rectangle";
				entity.sprite = loader.loadImage("img/" + entity.name + ".png");

				entity.breakSound = game.breakSound[entity.name];

				box2d.createRectangle(entity, definition);
				break;
			case "ground": // simple rectangles
				// No need for health. These are indestructible
				entity.shape = "rectangle";
				// No need for sprites. These won't be drawn at all
				box2d.createRectangle(entity, definition);
				break;
			case "hero":    // simple circles
			case "villain": // can be circles or rectangles
				entity.health = definition.fullHealth;
				entity.fullHealth = definition.fullHealth;
				entity.sprite = loader.loadImage("img/" + entity.name + ".png");
				entity.shape = definition.shape;

				entity.bounceSound = game.bounceSound;

				if (definition.shape === "circle") {
					entity.radius = definition.radius;
					box2d.createCircle(entity, definition);
				} else if (definition.shape === "rectangle") {
					entity.width = definition.width;
					entity.height = definition.height;
					box2d.createRectangle(entity, definition);
				}
				break;
			default:
				console.log("Undefined entity type", entity.type);
				break;
		}
	},

	// Take the entity, its position, and its angle and draw it on the game canvas
	draw: function (entity, position, angle) {
		// 先将绘图环境平移旋转，原点对准绘图的物体原点，绘制，再恢复初始状态
		game.context.translate(position.x * box2d.scale - game.offsetLeft, position.y * box2d.scale);
		game.context.rotate(angle);
		// box2d 在多边形之间留有空隙，多画 1px 掩盖掉绘制时的物体之间的空隙
		var padding = 1;

		switch (entity.type) {
			case "block":
				game.context.drawImage(entity.sprite, 0, 0, entity.sprite.width, entity.sprite.height,
					-entity.width / 2 - padding, -entity.height / 2 - padding, entity.width + 2 * padding, entity.height + 2 * padding);
				break;
			case "villain":
			case "hero":
				if (entity.shape === "circle") {
					game.context.drawImage(entity.sprite, 0, 0, entity.sprite.width, entity.sprite.height,
						-entity.radius - padding, -entity.radius - padding, entity.radius * 2 + 2 * padding, entity.radius * 2 + 2 * padding);
				} else if (entity.shape === "rectangle") {
					game.context.drawImage(entity.sprite, 0, 0, entity.sprite.width, entity.sprite.height,
						-entity.width / 2 - padding, -entity.height / 2 - padding, entity.width + 2 * padding, entity.height + 2 * padding);
				}
				break;
			case "ground":
				// Do nothing... We draw objects like the ground & slingshot separately
				break;
		}

		game.context.rotate(-angle);
		game.context.translate(-position.x * box2d.scale + game.offsetLeft, -position.y * box2d.scale);
	}

};

$(window).load(function () {
	game.init();
})